package io.gitee.zhangbinhub.acp.cloud.resource.server

import io.gitee.zhangbinhub.acp.boot.exceptions.ServletExceptionHandler
import io.gitee.zhangbinhub.acp.cloud.resource.server.component.AcpOpaqueTokenIntrospect
import io.gitee.zhangbinhub.acp.cloud.resource.server.conf.AcpCloudResourceServerConfiguration
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.acp.core.common.log.LogFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
import org.springframework.boot.autoconfigure.web.ServerProperties
import org.springframework.context.annotation.Bean
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.web.client.RestTemplate

/**
 * Oauth2 资源服务配置
 *
 * @author zhangbin by 11/04/2018 15:13
 * @since JDK 11
 */
@AutoConfiguration(
    before = [OAuth2ResourceServerAutoConfiguration::class],
    after = [AcpCloudResourceServerComponentAutoConfiguration::class]
)
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
class AcpCloudResourceServerServletAutoConfiguration(
    private val acpCloudResourceServerConfiguration: AcpCloudResourceServerConfiguration,
    private val acpCloudResourceServerComponentAutoConfiguration: AcpCloudResourceServerComponentAutoConfiguration,
    private val servletExceptionHandler: ServletExceptionHandler,
    private val oAuth2ResourceServerProperties: OAuth2ResourceServerProperties,
    @Qualifier("acpSpringCloudResourceServerRestTemplate")
    private val restTemplate: RestTemplate,
    serverProperties: ServerProperties
) {
    private val log = LogFactory.getInstance(this.javaClass)

    private val contextPath: String =
        if (CommonTools.isNullStr(serverProperties.servlet.contextPath)) "" else serverProperties.servlet.contextPath

    /**
     * http 验证策略配置
     *
     * @param httpSecurity http安全验证对象
     * @throws Exception 异常
     */
    @Bean
    @Throws(Exception::class)
    fun httpSecurityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
        val permitAll = acpCloudResourceServerComponentAutoConfiguration.webPermitAllPath()
        val security = ArrayList<String>()
        acpCloudResourceServerConfiguration.securityPath.forEach { path -> security.add(contextPath + path) }
        security.forEach { uri -> log.info("security uri: $uri") }
        permitAll.forEach { uri -> log.info("permitAll uri: $uri") }
        log.info("security uri: other any")
        // match 匹配的url，赋予全部权限（不进行拦截）
        httpSecurity.csrf {
            it.disable()
        }.authorizeHttpRequests {
            it.requestMatchers(*security.map { path -> AntPathRequestMatcher(path) }.toTypedArray()).authenticated()
                .requestMatchers(*permitAll.map { path -> AntPathRequestMatcher(path) }.toTypedArray()).permitAll()
                .anyRequest().authenticated()
        }.userDetailsService(
            InMemoryUserDetailsManager(
                User.builder()
                    .username(acpCloudResourceServerConfiguration.clientId)
                    .password(acpCloudResourceServerConfiguration.clientSecret)
                    .passwordEncoder {
                        PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(it)
                    }
                    .build()
            )
        ).httpBasic(Customizer.withDefaults())
        // 关闭session
        httpSecurity.sessionManagement { it.disable() }
        // 自定义远程 token 校验
        httpSecurity.oauth2ResourceServer {
            it.opaqueToken { opaqueToken ->
                opaqueToken.introspector(opaqueTokenIntrospect())
            }
            it.authenticationEntryPoint { _, response, authException ->
                servletExceptionHandler.responseGlobalException(response, authException)
            }
            it.accessDeniedHandler { _, response, accessDeniedException ->
                servletExceptionHandler.responseGlobalException(response, accessDeniedException)
            }
        }
        return httpSecurity.build()
    }

    @Bean
    @ConditionalOnMissingBean(OpaqueTokenIntrospector::class)
    fun opaqueTokenIntrospect() = AcpOpaqueTokenIntrospect(
        oAuth2ResourceServerProperties.opaquetoken.introspectionUri,
        restTemplate
    )
}
